package com.homestead.utils;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.homestead.result.RedisLockResult;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.redisson.api.*;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.StringCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author yhb
 *
 */
public class RedisUtil {
    private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);

    private static final RedissonClient redissonClient = SpringBeanFactory.getBean(RedissonClient.class);

    private static final ExecutorService executorService = new ThreadPoolExecutor(
            10,
            10,
            0L,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>());

    /**
     * 扣减redis库存操作
     * @param keyName key
     * @param amount 扣减数量
     * @param mode 扣减策列
     * @return 实际扣减数量(key不存在返回null)
     */
    public static Long decrStock(String keyName, long amount, RStore.DecrMode mode) {
        return redissonClient.getScript(LongCodec.INSTANCE).evalSha(RScript.Mode.READ_WRITE, LuaUtil.getSha("decrStock.lua"), RScript.ReturnType.INTEGER, Collections.singletonList(keyName), amount,  mode.getFlag());
    }

    public static Long decrStock(String keyName, long amount) {
        return decrStock(keyName, amount, RStore.DecrMode.DECR);
    }

    public static Long incrStock(String keyName, long amount) {
        return redissonClient.getScript(LongCodec.INSTANCE).evalSha(RScript.Mode.READ_WRITE, LuaUtil.getSha("incrStock.lua"), RScript.ReturnType.INTEGER, Collections.singletonList(keyName), amount);
    }

    public static void setStock(String keyName, long amount) {
        RAtomicLong atomicLong = redissonClient.getAtomicLong(keyName);
        atomicLong.set(amount);
    }

    /**
     *
     * @param keyName redis key
     * @param waitTime 等待时间 0不等待
     * @param expireTime redis过期时间
     * @param timeUnit 单位
     * @param supplier 业务代码提供者
     * @param <V> 返回类型
     * @return 返回
     * @throws Exception 异常
     */
    public static <V> RedisLockResult<V> executeTryLock(String keyName, int waitTime, int expireTime, TimeUnit timeUnit, MySupplier<V> supplier) throws Exception {
        RLock lock = redissonClient.getLock(keyName);
        // 上锁
        boolean lockFlag = lock.tryLock(waitTime, expireTime, timeUnit);

        if (!lockFlag) {
            return RedisLockResult.fail();
        }

        try {
            // 业务代码
            return RedisLockResult.success(supplier.get());
        } finally {
            try {
                lock.unlock();
            } catch (Exception e) {
                logger.error("RedisUtil#executeTryLock解锁失败，keyName={}", keyName, e);
            }
        }
    }

    public static <V> RedisLockResult<V> executeTryLock(RLock lock, int waitTime, int expireTime, TimeUnit timeUnit, MySupplier<V> supplier) throws Exception {
        // 上锁
        boolean lockFlag = lock.tryLock(waitTime, expireTime, timeUnit);

        if (!lockFlag) {
            return RedisLockResult.fail();
        }

        try {
            // 业务代码
            return RedisLockResult.success(supplier.get());
        } finally {
            try {
                lock.unlock();
            } catch (Exception e) {
                logger.error("RedisUtil#executeTryLock解锁失败，RLock name={}", lock.getName(), e);
            }
        }
    }

    public static <V> V executeLock(String keyName, int expireTime, TimeUnit timeUnit, MySupplier<V> supplier) {
        RLock lock = redissonClient.getLock(keyName);
        // 上锁
        lock.lock(expireTime, timeUnit);

        try {
            // 业务代码
            return supplier.get();
        } finally {
            try {
                lock.unlock();
            } catch (Exception e) {
                logger.error("RedisUtil#executeLock解锁失败，lockName={}", keyName, e);
            }
        }
    }

    public static <V> V executeLock(RLock rLock, int expireTime, TimeUnit timeUnit, MySupplier<V> supplier) {
        // 上锁
        rLock.lock(expireTime, timeUnit);

        try {
            // 业务代码
            return supplier.get();
        } finally {
            try {
                rLock.unlock();
            } catch (Exception e) {
                logger.error("RedisUtil#executeLock解锁失败，rLockName={}", rLock.getName(), e);
            }
        }
    }

    /**
     * 生成自增主键
     * @return Long
     */
    public static Long generateId() {
        String day = new SimpleDateFormat("yyMMdd").format(new Date());
        String keyName = "redis:generateId:" + day;
        long incrLong = 0;
        try {
            RAtomicLong rAtomicLong = redissonClient.getAtomicLong(keyName);
            incrLong = rAtomicLong.incrementAndGet();
            rAtomicLong.expire(DateUtil.betweenMs(new DateTime(), DateUtil.beginOfDay(DateUtil.tomorrow())) + 1, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            logger.error("redis生成分布式主键失败 message={}", e.getMessage(), e);
            throw e;
        }
        String incrVal = String.format("%010d", incrLong);
        return Long.parseLong(day + incrVal);
    }

    /**
     * 批量生成自增主键
     * @param count 生成数量
     * @return list 主键升序排列
     */
    public static Queue<Long> generateIds(int count) {
        String day = new SimpleDateFormat("yyMMdd").format(new Date());
        String keyName = "redis:generateId:" + day;
        long incrLong = 0;
        try {
            RAtomicLong rAtomicLong = redissonClient.getAtomicLong(keyName);
            incrLong = rAtomicLong.addAndGet(count);
            rAtomicLong.expire(DateUtil.betweenMs(new DateTime(), DateUtil.beginOfDay(DateUtil.tomorrow())) + 1, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            logger.error("redis批量生成分布式主键失败 eMessage={}", e.getMessage(), e);
            throw e;
        }
        String incrVal = String.format("%010d", incrLong - count + 1);
        Queue<Long> ids = new ArrayDeque<>(count);
        long baseLong = Long.parseLong(day + incrVal);
        for (int i = 0; i < count; i++) {
            ids.add(baseLong + i);
        }
        return ids;
    }

    /**
     * 获取一个随机的过期时间（秒）
     * @param min 最小的时间数
     * @param max 最大的时间数
     * @param timeUnit  入参min和max的时间单位
     * @return 随机秒数
     */
    public static long generateRandomExpireSeconds(long min, long max, TimeUnit timeUnit) {
        return RandomUtils.nextLong(timeUnit.toSeconds(min), timeUnit.toSeconds(max));
    }

    /**
     * 获取明天指定时间段的随机过期时间
     * @param min 最小的时间数
     * @param max 最大的时间数
     * @param timeUnit 入参min和max的时间单位
     * @return 随机秒数
     */
    public static long tomorrowRandomExpireSeconds(long min, long max, TimeUnit timeUnit) {
        // 次日凌晨偏移当前时间的秒数
        long tomorrowOffsetNowSeconds = DateUtil.between(DateTime.now(), DateUtil.beginOfDay(DateUtil.tomorrow()), DateUnit.SECOND);
        // += 指定时间段的随机时间
        tomorrowOffsetNowSeconds += generateRandomExpireSeconds(min, max, timeUnit);
        return tomorrowOffsetNowSeconds;
    }

    /**
     * 设置指定日期的过期时间
     * @param expireDate 指定的日期
     * @return 秒
     */
    public static long specifiedDateExpireSeconds(Date expireDate) {
        DateTime beginDate = DateTime.now();
        if (beginDate.after(expireDate)) {
            throw new IllegalArgumentException("expireDate不能早于当前时间 expireDate=" + DateUtil.format(expireDate, DatePattern.NORM_DATETIME_PATTERN));
        }
        return DateUtil.between(beginDate, expireDate, DateUnit.SECOND);
    }
}
